package top.techqi.core.tool.xlog.impl

import android.os.Build
import android.util.Log
import top.techqi.core.App
import top.techqi.core.kext.MINUTES
import top.techqi.core.tool.xlog.ILog
import java.io.File
import java.io.FileWriter
import java.io.PrintWriter
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.concurrent.Executors

/**
 * 日志文件输出
 *
 * @param folder 日志文件存放目录
 * @param prefix 日志文件名前缀
 * @param suffix 日志文件名后缀
 * @param rotate 日志文件滚动时间间隔，单位分钟，默认20分钟
 */
@Suppress("NOTHING_TO_INLINE")
class FileLog(
    private val folder: File,
    private val prefix: String = "log_",
    private val suffix: String = ".txt",
    private val rotate: Int = 20,
) : ILog {
    private val executor by lazy { Executors.newSingleThreadExecutor() }

    private val defHeader: Map<String, Any?> by lazy {
        linkedMapOf(
            "App PackageName" to App.packageName,
            "App VersionName" to App.versionName,
            "App VersionCode" to App.versionCode,
            "Device Manufacturer" to Build.MANUFACTURER,
            "Device Model" to Build.MODEL,
            "Android Version" to Build.VERSION.RELEASE,
            "Android SDK" to Build.VERSION.SDK_INT,
        )
    }

    override fun v(tag: String, msg: String, tr: Throwable?) {
        generateLogFile(System.currentTimeMillis(), V, tag, msg, tr)
    }

    override fun d(tag: String, msg: String, tr: Throwable?) {
        generateLogFile(System.currentTimeMillis(), D, tag, msg, tr)
    }

    override fun i(tag: String, msg: String, tr: Throwable?) {
        generateLogFile(System.currentTimeMillis(), I, tag, msg, tr)
    }

    override fun w(tag: String, msg: String, tr: Throwable?) {
        generateLogFile(System.currentTimeMillis(), W, tag, msg, tr)
    }

    override fun e(tag: String, msg: String, tr: Throwable?) {
        generateLogFile(System.currentTimeMillis(), E, tag, msg, tr)
    }

    private fun generateLogFile(
        time: Long, prio: Int, tag: String, msg: String?, tr: Throwable?
    ) = executor.submit {
        try {
            folder.exists() || folder.mkdirs()

            val date = Date(time - time % (rotate.MINUTES))
            val name = FMT_FILENAME.format(date)
            val file = File(folder, "$prefix$name$suffix")

            val needCreate = !file.exists()
            PrintWriter(FileWriter(file, true), true).use {
                if (needCreate) printHeader(it)

                it.print(FMT_FILEHEAD.format(time))
                it.print(" ")
                it.print(LOG_TYPECODE[prio - V])
                it.print("/")
                it.print(tag)
                it.print(": ")
                it.println(msg)
                printStacks(it, tr)
                it.println("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^")
                it.println()
            }
        } catch (ex: Throwable) {
            Log.w("FileLog", "generateLogFile", ex)
        }
    }

    private inline fun printHeader(writer: PrintWriter) {
        val header = LinkedHashMap(defHeader)
        writer.println("==================== Log Head ====================")
        header.forEach { (key, value) ->
            writer.print(key.padEnd(20))
            writer.print(": ")
            writer.println(value)
        }
        writer.println("==================== Log Head ====================")
        writer.println()
    }

    private inline fun printStacks(writer: PrintWriter, throwable: Throwable?) {
        var tr: Throwable? = throwable
        while (tr != null) {
            // tr.printStackTrace(it)
            // printStackTrace打印的栈信息可能不全，因此使用下面自定义的方式打印
            writer.println(tr)
            val traces = tr.stackTrace
            traces.forEach { trace ->
                writer.print("\tat ")
                writer.println(trace)
            }
            tr = tr.cause
        }
    }

    companion object {
        private val FMT_FILENAME = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.ENGLISH)
        private val FMT_FILEHEAD = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.ENGLISH)
        private val LOG_TYPECODE = charArrayOf('V', 'D', 'I', 'W', 'E')

        private const val V = Log.VERBOSE
        private const val D = Log.DEBUG
        private const val I = Log.INFO
        private const val W = Log.WARN
        private const val E = Log.ERROR
    }
}
